/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package edu.mit.csail.sdg.alloy4;

import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;

/** This provides a convenience wrapper around a Process object.
 *
 * <p> To launch a subprocess, simply write Subprocess.exec(timeout, args);
 *
 * <p><b>Thread Safety:</b>  Safe.
 */

public final class Subprocess {

   /** Timer used to schedule a timeout for the process. */
   private static final Timer stopper = new Timer();

   /** This field will store the program output (or "" if an error occurred) */
   private String stdout = null;

   /** This field will store an error message (or "" if no error occurred) */
   private String stderr = null;

   /** Constructor is private since we only allow the static method exec() to construct objects of this class. */
   private Subprocess() { }

   /** Executes the given command line, wait for its completion, then return its output.
    * @param timeLimit - we will attempt to terminate the process after that many milliseconds have passed
    * @param p - preconstructed Process object
    */
   private static String exec (final long timeLimit, final Process p) throws Exception {
      final Subprocess pro = new Subprocess();
      final InputStream f1 = p.getInputStream(), f2 = p.getErrorStream();
      TimerTask stoptask = new TimerTask() {
         public void run() {
            synchronized(pro) { if (pro.stdout!=null && pro.stderr!=null) return; pro.stdout=""; pro.stderr="Error: timeout"; }
            p.destroy();
         }
      };
      synchronized(Subprocess.class) { stopper.schedule(stoptask, timeLimit); }
      new Thread(new Runnable() {
         public void run() {
            String err = null;
            try { if (f2.read()>=0) err="Error: stderr"; } catch(Throwable ex) { err="Error: "+ex; }
            synchronized(pro) { if (err!=null) {pro.stdout=""; pro.stderr=err;} else if (pro.stderr==null) pro.stderr=""; }
         }
      }).start();
      p.getOutputStream().close();
      StringBuilder output = new StringBuilder();
      byte[] buf = new byte[8192];
      while(true) {
         int n = f1.read(buf);
         if (n<0) break; else for(int i=0; i<n; i++) output.append((char)(buf[i]));
      }
      synchronized(pro) { if (pro.stdout==null) pro.stdout=output.toString(); }
      for(int i=0; i<10; i++) {
         synchronized(pro) { if (pro.stderr!=null) return pro.stderr + pro.stdout; }
         Thread.sleep(500);
      }
      return "Error: wait timeout";
   }

   /** Executes the given command line, wait for its completion, then return its output.
    * @param timeLimit - we will attempt to terminate the process after that many milliseconds have passed
    * @param commandline - the command line
    */
   public static String exec (final long timeLimit, final String[] commandline) {
      Process p = null;
      try {
         p = Runtime.getRuntime().exec(commandline);
         return exec(timeLimit, p);
      } catch (Throwable ex) {
         return "Error: "+ex;
      } finally {
         if (p!=null) p.destroy();
      }
   }
}
